# @Time : 2024-10-19 17:23
# @Author : Fioman 
# @Phone : 13149920693
"""
高阶函数:
一等公民的理解:
1) 函数可以作为另外一个函数的参数和返回值
2) 可以赋值给一个变量
3) 函数变量可以被修改,指向另外的一个地址(可能是函数,也可能是普通的数据类型)
4) 可以存储在数据结构中(如列表,字典等)中
"""
import time


# region 1. 函数作为参数
def sum_numbers(nums):
    return sum(nums)


def higher_order_function(f, lst):  # 将函数作为参数
    sumation = f(lst)
    return sumation


result = higher_order_function(sum_numbers, [1, 2, 3, 4, 5])
print(result)


# endregion

# region 2. 函数作为返回值
def add(x, y):
    return x + y


def subtract(x, y):
    return x - y


def multiply(x, y):
    return x * y


def divide(x, y):
    return x / y


def get_calc_func(calcType):
    if calcType == "add":
        return add
    elif calcType == "subtract":
        return subtract
    elif calcType == "multiply":
        return multiply
    else:
        return divide


calcFunc = get_calc_func("add")
print(f"add: {calcFunc(1, 2)}")
calcFunc = get_calc_func("divide")
print(f"divide: {calcFunc(10, 2)}")
calcFunc = get_calc_func("multiply")
print(f"multiply: {calcFunc(10, 2)}")


# endregion

# region 3. 全局变量和局部变量
# 1) 局部变量: 定义在函数内部或者作为函数的形参的变量就是局部变量
#                      局部变量只能在函数内部使用,在函数外部不可以访问

def func(a, b):
    print(f"局部变量a :{a},局部变量b:{b}")
    c = "局部变量c"
    print(f"局部变量c: {c}")
    print(f"全局变量x: {x},全局变量y:{y}")


x = 10
y = 20
print(f"全局变量x: {x},全局变量y:{y}")
func(x, y)

# print(a)
# print(b)
# print(c) 没有办法访问局部变量

# 2) 全局变量: 定义在函数的外部,模块内部为全局变量,模块内的所有的函数都可以访问全局变量
#                      在函数的内部,如果没有global声明只能访问全局变量,不能直接修改赋值.如果没有
#                      global声明,会被当成创建了一个新的同名的局部变量.Global语句在声明为全局
#                       变量之前不能使用,并且形参里不能和Global声明的全局变量同名

# def func2(a, b):
#     global a  错误,不能和形参同名

# def func3(x, y):
#     print("a = {}".format(a))  # 错误,global变量在声明之前不能被使用
#     global a, b


a = 10
b = 20


# 3) nonlocal 外部嵌套函数作用域. 嵌套函数的外层函数的作用域,称为外部嵌套函数作用域
# 非局部,非全局的作用域
def outer(a, b):
    c = 10

    def inner(d, e):
        print(a, b, c, d, e)

    return inner


# a,b,c就是外部嵌套函数作用域
innerFunc = outer(1, 2)
innerFunc(7, 8)
innerFunc(5, 6)  # 1,2,10这三个外部嵌套函数作用域变量会一直存在,不会随着函数的消失而消失


# endregion

# region 4. Python 4个作用域原则 LEGB
# 1) Local 局部作用域 本地作用域L
# 2) Enclosing 外部嵌套函数作用域 E
# 3) Global 全局作用域G
# 4) Builtin Python内置模块作用域B

def funcX(a, b):
    c = 100
    print(f"局部作用域变量: {a},{b},{c}")


def outer(x, y):
    z = 1000

    def inner(a, b):
        print("局部作用域变量访问", a, b)
        print("外部嵌套函数作用域变量访问:", x, y, z)

    return inner


func = outer(1, 2)
func(100, 200)

globalVal = 100
print("全局变量访问: ", globalVal)

print("内置模块作用域变量访问: ", abs)


# endregion

# region 5. Python的变量名的查找顺序
# Local > Enclosing > Global > Builtin
# 局部作用域 > 包裹这个函数的外部嵌套函数作用域 > 全局作用域 > 内置模块作用域
# endregion

# region 6. nonlocal 语句
# 声明的变量既不是全局变量也不是局部变量,而是外部嵌套函数作用域变量
# nonlocal只能在被嵌套的函数的内部使用
# nonlocal 变量将对外部嵌套函数作用域变量进行操作
# 当有两层非嵌套的时候,访问nonlocal变量时只对变量的最近一层的变量进行操作
# nonlocal声明的变量不能出现在形参列表中(谁的形参列表,内层函数还是外层函数)

# 为什么需要nonlocal,如果没有nonlocal,我们可以访问外部嵌套函数作用域的变量,比如下面这种
def outer02(x, y):
    z = 100

    def inner():
        print("访问外部嵌套函数作用域内的变量,不需要nonlocal,直接可以访问: ", x, y, z)

    return inner


func = outer02(10, 20)
func()


# 但是如果我们需要修改外部嵌套函数作用域内的变量,怎么去修改呢?
def outer03(x, y):
    z = 100

    def inner():
        x = 1
        y = 2
        z = 3
        print("x,y,z", x, y, z)

    return inner


func = outer03(100, 200)
func()  # 1,2,3


# 那么怎么去证明呢,证明里面的x,y,z和外面的x,y,z不是同一个变量呢
def outer04(x, y):
    z = 100

    def inner():
        nonlocal x, y, z
        x += 10
        y += 10
        z += 10
        print(f"x,y,z: {x, y, z}")

    return inner


func = outer04(100, 200)
func()  # (110, 210, 110)
func()  # (120, 220, 120)


# endregion

# region 7. 闭包的概念 内层函数引用了外层函数作用域中的变量就称为闭包.
# 闭包针对的是变量,一般来说我们函数访问外部的变量只能是全局变量,这里的外部针对的就是函数外部(非局部)
# 而全局变量有一个坏处就是什么地方都可能会被修改,而闭包就是通过一个函数提供这个变量,可以让内层函数
# 使用,这个变量和内层函数是绑定关系,只要这个内层函数在使用,这个外层的变量就一直存在.
# 1) 必须是嵌套函数  2) 内层函数必须引用了外层嵌套函数中的变量 3) 外层嵌套函数的返回值必须是内层函数

# 简单闭包实现
def calc_func(x):
    def inner(y):
        return y ** x

    return inner


# 实现立方的功能
func = calc_func(3)
res = func(3)
print("立方结果: ", res)
# 实现平方的功能
func = calc_func(2)
res = func(3)
print(f"平方结果: {res}")
res = func(5)  # 外部作用域的变量2是一直存在的,内部函数可以一直使用,并且更改,其他地方又访问不到杜绝了
# 全局变量的缺点
print(f"平方结果: {res}")


# endregion

# region 8. 如何判断一个函数是否是闭包函数 通过__closure__属性
def outer5(x):
    y = 100

    def inner():
        print("我是内部嵌套函数,没有使用外部变量")

    return inner


func = outer5(3)
print(func.__closure__)  # 返回None


def outer6(x):
    y = 100

    def inner():
        print(f"我是内部嵌套函数,我使用了外部变量: {x},{y}")

    return inner


func = outer6(3)
print(func.__closure__)  # (<cell at 0x0000023A3C3BBEE8: int object at 0x00007FF80C939380>,
# <cell at 0x0000023A3C3BB8E8: int object at 0x00007FF80C939FA0>)
"""
1) 嵌套函数: 内层函数在函数的外部不能直接使用,必须是通过外层函数去返回才可以.
2) 函数名的本质: 就是一个地址的别名,这个地址存放了函数所在的内存地址
"""
# endregion

# region 9. 装饰器 (闭包 + 函数作为形参)
"""
1) 装饰器就是一个闭包函数,只是外层函数的参数一般包括一个函数对象
2) 传入一个函数,在不改变原来函数的内容的前提下为其添加一些额外的功能
3) 装饰器的内层函数在调用传入函数的前后,添加我们需要的代码,然后为其添加我们需要添加的功能
4) 装饰器本质上是函数名和函数地址的重新绑定
5) 装饰器函数是一个闭包函数,其实它最终指向的是内层函数的位置
"""


# 比如一个统计时间的装饰器,每个函数加上这个装饰器就可以统计函数的运算时间
def time_count_deco(func):
    def inner(args):
        startTime = time.time()
        res = func(args)
        endTime = time.time()
        print(f"计算结果: {res},耗时:{endTime - startTime}")

    return inner


# 相当于是 add_some_numbers = time_count_deco(add_some_numbers)
@time_count_deco
def add_some_numbers(args):
    return sum(args)


add_some_numbers(range(10))


# endregion

# region 10. 装饰器的通用写法(接受任意的参数)
def deco_func(func):
    def inner(*args, **kwargs):
        print("函数调用之前添加的功能")
        ret = func(*args, **kwargs)
        print("函数调用之后添加的功能")
        return ret

    return inner


@deco_func
def func1(a, b, c=2):
    return a + b + c


ret = func1(1, 2, 3)
print("ret = {}".format(ret))
# 但是这样有个不好的地方就是func1的名字被改变了,比如我们去打印这个函数的名字的时候会发现发生了变化
print("func1.__name__ = ", func1.__name__)
# 这也是可以理解的,因为它的指向发生了变化,实际上func1 = inner 了,如何才能让其名字不改变呢,需要使用
# 内层加上warps
# endregion

# region 11. 不改变函数名字的装饰器写法 wraps
from functools import wraps


def name_not_change_deco(func):
    @wraps(func)
    def inner(*args, **kwargs):
        print("函数调用之前添加的功能")
        ret = func(*args, **kwargs)
        print("函数调用之后添加的功能")
        return ret

    return inner


@name_not_change_deco
def add_nums(a, b, c, d, e, f=10):
    return a + b + c + d + e + f


res = add_nums(1, 2, 3, 4, 5)
print("res = {}".format(res))
print("add_nums.__name__ = ", add_nums.__name__)  # add_nums


# endregion

# region 12. 多层装饰器的使用
def deco01(func):
    def inner(*args, **kwargs):
        print("in deco01()  被装饰函数调用之前被调用")
        ret = func(*args, **kwargs)
        print("in deco01()  被装饰函数调用之后被调用")
        return ret

    return inner


def deco2(func):
    def inner(*args, **kwargs):
        print("in deco02()  被装饰函数调用之前被调用")
        ret = func(*args, **kwargs)
        print("in deco02()  被装饰函数调用之后被调用")
        return ret

    return inner


# 这里相当于是    f = deco01(f)   f = deco2(deco01(f))  所以最终的调用顺序是
# deoc2 中的第一条打印先调用,然后调用了deco01中第一条打印,再然后是执行f方法,打印.
# 然后是deco01的第二条打印,最后是deco2的第二条打印
@deco2
@deco01
def f():
    print(f"被装饰函数被调用")


f()


# endregion

# region 13. map函数的使用 map(func,iterable1,iterable2)
# 功能: 将一个函数作用于一个或者多个可迭代对象,并返回一个迭代器.主要用于列表或者元组的映射操作.
# 1) 函数参数的个数和可迭代对象的个数是一致的.
# 2) 如果参数的长度不一致,以最短的那个参数为准,最短的参数是多少,就迭代多少次

# 单个可迭代对象
def square(x):
    return x ** 2


numbers = [1, 2, 3, 4, 5]
result = map(square, numbers)
print(list(result))
# 使用lambda表达式
result = map(lambda x: x ** 2, numbers)
print(list(result))

# 使用列表推导式
result = [x ** 2 for x in numbers]
print(result)


# 多个可迭代对象
def my_add(x, y):
    return x + y


nums01 = [1, 2, 3]
nums02 = [4, 5, 6, 7, 8]
result = map(my_add, nums01, nums02)
print(list(result))
result = map(lambda x: x ** 2, nums02)
print(list(result))
result = [x + y for x, y in zip(nums01, nums02)]
print(result)
# endregion

# region 14. Filter() 接收一个返回bool值的函数,保留为true的结果.
# 语法Filter(func,iterable)  将iterable迭代带入到func,如果为True就保留,否则就丢弃
# 1) 筛选出偶数
nums = [x for x in range(10)]
result = filter(lambda x: x % 2 == 0, nums)
print(list(result))
nums = [x for x in nums if x % 2 == 0]  # 可以用列表推导式来代替
print(nums)
strings = ["apple", "banana", None, "City"]
result = filter(lambda s: s and s.strip(), strings)
print(list(result))
result = [s for s in strings if s is not None and s.strip()]
print(result)
# 2) function为None的时候,就自动会判断iterable里面的值,只保留为True的值
items = [0, 1, False, 2, "", 3, None, 4, [], set(), tuple(), 5, dict()]
result = filter(None, items)
print(list(result))  # [1, 2, 3, 4, 5]
result = [item for item in items if item]
print(result)  # [1, 2, 3, 4, 5]
# 所以可以看出来 0 False, "",None,[],空集合,空元组,空字典,都是返回的False
# endregion

# region 15. reduce(func,iterable,initializer=None) 函数
# func是一个带有两个参数的函数,它会连续的作用于序列中的元素
# 将上一次的结果和下一个元素带入到func进行计算最后得到结果
# 如果提供了initialize的值,第一个带入的是第一个元素和initalizer
# 如果没有提供initialize,第一个带入的是第一个元素和第二个元素,第二次带入的就是上一次的结果和第三个元素
from functools import reduce


def multy(x, y):
    return x * y


nums = [1, 2, 3, 4, 5]
result = reduce(multy, nums)
print(result)
result = reduce(multy, nums, 100)
print(result)
result = reduce(lambda x, y: x * y, nums)
print(result)
words = ["Hello", "World", "Python", "is", "awesome"]
result = reduce(lambda x, y: x + " " + y, words)
print(result)

# endregion

# region 16. zip(*iterables) 将任意数量的可迭代对象打包成一个个元组迭代器,适用于多个迭代对象并行遍历
# 也是以最短的作为结束,剩下的元素会被忽略掉
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35, 24, 56]
result = zip(names, ages)
print(f"result :{result},type(result) = {type(result)}")
# zip() 和 *解包操作 ,这个比较厉害
pairs = [('Alice', 25), ('Bob', 30), ('Charlie', 35)]
# 将三个元组,组合成两个元组
names, ages = zip(*pairs)  # 这个可以看成是提取
print(names, ages)
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35, 24, 56]
pairs = zip(names, ages)
for name, age in pairs:
    print(name, age)
# 转换为字典
result = dict(zip(names, ages))
# endregion
